from socketserver import ThreadingMixIn
import threading
import queue as Queue


class ThreadPoolingMixIn(ThreadingMixIn):
 """
 Mix-in class to handle requests in a thread
 pool.
 
 The pool grows and thrinks depending on
 load.
 
 For instance, a threading UDP server class is created as
 follows:
 
 class ThreadPoolingUDPServer(ThreadPoolingMixIn, UDPServer):
 pass
 
 """
 __author__ = 'Pavel Uvarov <pavel.uvarov@gmail.com'
 
 def init_thread_pool(self, min_workers = 5, max_workers = 100, min_spare_workers = 5):
   """Initialize thrad pool."""
   self.request_queue = Queue.Queue()
   self.min_workers = min_workers
   self.max_workers = max_workers
   self.min_spare_workers = min_spare_workers
   self.num_workers = 0
   self.num_busy_workers = 0
   self.workers_mutex = threading.Lock()
   self.start_workers(self.min_workers)
 
 def start_workers(self, n):
   """Start n workers."""
   for i in range(n):
     t = threading.Thread(target = self.worker)
     t.setDaemon(True)
     t.start()
 
 def worker(self):
   """
      A function of a working thread.
      Checks queue, blocks if there are no requests,
      and processes request if there is one.
   
      After completing a request, it checks how many spare
      workers there are and if there are more than
      min_spare_workers, the worker exits.
      
      Otherwise, the worker loops infinitely.
   """
   with self.workers_mutex:
     self.num_workers += 1
   while True:
     (request, client_address) = self.request_queue.get()
     with self.workers_mutex:
       self.num_busy_workers += 1
    
     self.process_request_thread(request, client_address)
     self.request_queue.task_done()
     
     with self.workers_mutex:
       self.num_busy_workers -= 1
       if self.num_workers-self.num_busy_workers > self.min_spare_workers:
         self.num_workers -= 1
         return
 
 def process_request(self, request, client_address):
   """
     Puts a request into the queue.
     If the queue is too long, add extra workers.
   """
   self.request_queue.put((request, client_address))
   with self.workers_mutex:
     if self.request_queue.qsize() > 3 and self.num_workers < self.max_workers:
       self.start_workers(1)      
 
 def join(self):
   """ Wait for all busy threads. """
   with self.workers_mutex:
    print("Waiting on ", self.num_busy_workers, "ongoing connections before exiting.")
   self.request_queue.join()
   
    
 